// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2025 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
 */

/*\
 * Test for CVE-2025-38236 fixed in kernel v6.16-rc4:
 * 32ca245464e1 ("af_unix: Don't leave consecutive consumed OOB skbs").
 *
 * The bug is triggered by sending multiple out-of-band data to a socket and
 * reading it back from it. According to the MSG_OOB implementation, this
 * shouldn't be possible. When system is affected by CVE-2025-38236, instead,
 * skb queue holds MSG_OOB data, breaking recv() and causing a use-after-free
 * condition.
 *
 * Even if MSG_OOB is mostly used inside Oracle's product, it is enabled by
 * default in linux kernel via CONFIG_AF_UNIX_OOB. This is accessible via
 * Chrome's renderer sandbox, which might cause an attacker to escalate and to
 * obtain privileges in the system.
 *
 * Reproducer is based on:
 * https://project-zero.issues.chromium.org/issues/423023990
 */

#include "tst_test.h"

static char dummy;
static int sock[2];

static void run(void)
{
	int ret;

	dummy = '\0';

	tst_res(TINFO, "#1 send and receive out-of-band data");
	SAFE_SEND(0, sock[1], "A", 1, MSG_OOB);
	SAFE_RECV(0, sock[0], &dummy, 1, MSG_OOB);

	tst_res(TINFO, "#2 send and receive out-of-band data");
	SAFE_SEND(0, sock[1], "B", 1, MSG_OOB);
	SAFE_RECV(0, sock[0], &dummy, 1, MSG_OOB);

	tst_res(TINFO, "Send out-of-band data");
	SAFE_SEND(0, sock[1], "C", 1, MSG_OOB);

	tst_res(TINFO, "Receive data from normal stream");

	ret = recv(sock[0], &dummy, 1, MSG_DONTWAIT);
	if (ret == -1) {
		if (errno == EWOULDBLOCK) {
			tst_res(TPASS, "Can't read out-of-band data from normal stream");
			return;
		}

		tst_brk(TBROK | TERRNO, "recv error");
	}

	const char *msg = "We are able to read out-of-band data from normal stream";

	if (dummy == 'C') {
		tst_res(TFAIL, "%s", msg);
	} else {
		tst_res(TFAIL, "%s, but data doesn't match: '%c' != 'A'",
			msg, dummy);
	}

	SAFE_RECV(0, sock[0], &dummy, 1, MSG_OOB);

	tst_res(TFAIL, "We are able to access data from skb queue (use-after-free)");
}

static void setup(void)
{
	SAFE_SOCKETPAIR(AF_UNIX, SOCK_STREAM, 0, sock);
}

static void cleanup(void)
{
	if (sock[0] != -1)
		SAFE_CLOSE(sock[0]);

	if (sock[1] != -1)
		SAFE_CLOSE(sock[1]);
}

static struct tst_test test = {
	.test_all = run,
	.setup = setup,
	.cleanup = cleanup,
	.needs_kconfigs = (const char *[]) {
		"CONFIG_AF_UNIX_OOB=y",
		NULL
	},
	.tags = (const struct tst_tag[]) {
		{"linux-git", "32ca245464e1"},
		{"CVE", "2025-38236"},
		{}
	}
};
